#include "Collection.h"

Coleccion new(){
	Coleccion unaColeccion;
	unaColeccion.nodo = NULL;
	return unaColeccion;
}

int size(Coleccion unaColeccion){
	Nodo* nodoContador = unaColeccion.nodo;
	int contador = 0;
	for(; notNull(nodoContador); nodoContador = nodoContador->siguiente){
		contador = contador + 1;
	}
	return contador;
}

Bool isNull(Nodo* unNodo){
	return unNodo == NULL;
}

Bool notNull(Nodo* unNodo){
	return !isNull(unNodo);
}

Bool isEmpty(Coleccion unaColeccion){
	return isNull(unaColeccion.nodo);
}

Bool notEmpty(Coleccion unaColeccion){
	return !isEmpty(unaColeccion);
}

Nodo* first(Coleccion unaColeccion){
	return unaColeccion.nodo;
}

Nodo* last(Coleccion unaColeccion){
	Nodo* nodoRecorredor = unaColeccion.nodo;
	Nodo* ultimoNodo = NULL;
	for (; notNull(nodoRecorredor); nodoRecorredor = nodoRecorredor->siguiente){
		ultimoNodo = nodoRecorredor;
	}
	return ultimoNodo;
}

Nodo* newNodo(void* unElemento){
	Nodo* nuevoNodo = (Nodo*)malloc(sizeof(nuevoNodo));
	nuevoNodo->elemento = unElemento;
	nuevoNodo->siguiente = NULL;
	return nuevoNodo;
}

void addElement(Coleccion* unaColeccion, void* unElemento){
	Nodo* nodoAAgregar = newNodo(unElemento);
	if (isEmpty(*unaColeccion)){
		unaColeccion->nodo = nodoAAgregar;
	}else{
		Nodo* ultimoNodo = last(*unaColeccion);
		ultimoNodo->siguiente = nodoAAgregar;
	}
}

void removeElement(Coleccion* unaColeccion, void* unElemento){
	Nodo* unNodo = unaColeccion->nodo;
	Nodo* nodoAnterior = NULL;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		if (unNodo->elemento == unElemento){
			if (notNull(nodoAnterior)){
				nodoAnterior->siguiente = unNodo->siguiente;
			}else{
				unaColeccion->nodo = unNodo->siguiente;
			}
			free(unNodo);
			return;
		}
		nodoAnterior = unNodo;
	}
}

void forEach(Coleccion unaColeccion, void unaFuncionParaHacerAlgo(void*)){
	Nodo* unNodo = unaColeccion.nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		unaFuncionParaHacerAlgo(unNodo->elemento);
	}
}

Coleccion allButFirst(Coleccion unaColeccion){
	Coleccion cola = new();
	Nodo* unNodo = unaColeccion.nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		addElement(&cola,unNodo->elemento);
	}
	Nodo* primerNodo = first(unaColeccion);
	removeElement(&cola,primerNodo->elemento);
	return cola;
}

Coleccion allButLast(Coleccion unaColeccion){
	Coleccion cola = new();
	Nodo* unNodo = unaColeccion.nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		addElement(&cola,unNodo->elemento);
	}
	Nodo* ultimoNodo = last(unaColeccion);
	removeElement(&cola,ultimoNodo->elemento);
	return cola;
}

Coleccion map(Coleccion unaColeccion, void* unaFuncionTransformacion(void*)){
	Coleccion collectColeccion = new();
	Nodo* unNodo = unaColeccion.nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		addElement(&collectColeccion, unaFuncionTransformacion(unNodo->elemento));
	}
	return collectColeccion;
}

Coleccion filter(Coleccion unaColeccion, Bool unaFuncionCriterio(void*)){
	Coleccion selectColeccion = new();
	Nodo* unNodo = unaColeccion.nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		if (unaFuncionCriterio(unNodo->elemento)){
			addElement(&selectColeccion, unNodo->elemento);
		}
	}
	return selectColeccion;
}

Coleccion concat(Coleccion coleccion1, Coleccion coleccion2){
	Coleccion concatenacion = new();
	Nodo* unNodo = coleccion1.nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		addElement(&concatenacion, unNodo->elemento);
	}
	unNodo = coleccion2.nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		addElement(&concatenacion, unNodo->elemento);
	}
	return concatenacion;
}

void* concate(void* coleccion1, void* coleccion2){
	Coleccion* col1 = (Coleccion*)coleccion1;
	Coleccion* col2 = (Coleccion*)coleccion2;
	Coleccion concatenacion = new();
	Nodo* unNodo = col1->nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		addElement(&concatenacion, unNodo->elemento);
	}
	unNodo = col2->nodo;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		addElement(&concatenacion, unNodo->elemento);
	}
	Coleccion* punteroColeccion = (Coleccion*)malloc(sizeof(punteroColeccion));
	(*punteroColeccion) = concatenacion;
	return punteroColeccion;
}

void* foldl(Coleccion unaColeccion, void* valorInicial, void* unaFuncion(void*, void*)){
	Nodo* unNodo = unaColeccion.nodo;
	void* acumulador = valorInicial;
	for (; notNull(unNodo); unNodo = unNodo->siguiente){
		acumulador = unaFuncion(acumulador, unNodo->elemento);
	}
	return acumulador;
}

void* foldl1(Coleccion unaColeccion, void* unaFuncion(void*, void*)){
	Nodo* cabeza = first(unaColeccion);
	Coleccion cola = allButFirst(unaColeccion);
	return foldl(cola, cabeza->elemento, unaFuncion);
}

